/**
 * Author:yuiop
 * Date:2022/8/2 15:11
 */
package com.devops.aspect;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.json.JSONUtil;
import com.devops.admin.service.DOSLogRecordService;
import com.devops.common.annotation.DOSLog;
import com.devops.common.model.DOSLogRecord;
import com.devops.common.model.DOSLogSubject;
import com.devops.constant.DevOpsConstant;
import com.devops.shiro.vm.BaseUserLoginVm;
import com.devops.shiro.vm.BaseUserVM;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;

/**
 * @author hepingguo
 */
@Component
@Aspect
@Slf4j
public class DOSLogAspect extends BaseAspectSupport {

    @Autowired
    private DOSLogRecordService dosLogRecordService;

    @Pointcut("@annotation(com.devops.common.annotation.DOSLog)")
    public void logPointCut(){}


    @Around("logPointCut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {

        // 获取登录人员信息
        BaseUserVM user = (BaseUserVM) SecurityUtils.getSubject().getPrincipal();

        DOSLogSubject logSubject = new DOSLogSubject();
        if (user != null) {
            logSubject.setUserId(user.getId());
        }
        // 接口请求时间
        logSubject.setStartTime(DateUtil.now());
        //记录时间定时器
        TimeInterval timer = DateUtil.timer(true);
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        //IP地址
        String ip = getRemoteHost(request);
        logSubject.setIp(ip);
        String url = request.getRequestURL().toString();
        logSubject.setUrl(url);
        //请求方法名
        Method method = resolveMethod(joinPoint);
        // 获取logs
        DOSLog logs = method.getAnnotation(DOSLog.class);
        logSubject.setDescription(logs.description());
        logSubject.setMethod(method.getDeclaringClass().getName() + ":" + method.getName());
        //请求参数
        Object parameter = getParameter(method, joinPoint.getArgs());
        logSubject.setParameter(JSONUtil.parse(parameter));
        //执行结果
        Object result = null;
        try {
            result = joinPoint.proceed();
            logSubject.setResult(result);
            logSubject.setResultStatus(1);

        } catch (Exception e) {
            logSubject.setResult(e.getMessage());
            logSubject.setResultStatus(0);
            throw e;
        } finally {
            //执行消耗时间
            String endTime = timer.intervalPretty();
            logSubject.setSpendTime(endTime);
            DOSLogRecord logRecord = DOSLogRecord.builder()
                    .logInfo(logSubject)
                    .logType(logs.logType())
                    .method(logSubject.getMethod())
                    .resultStatus(logSubject.getResultStatus())
                    .createTime(LocalDateTime.now())
                    .build();
            if (user != null){
                logRecord.setUserId(user.getId());
            } else {
                if ("login".equalsIgnoreCase(joinPoint.getSignature().getName())) {
                    Optional<BaseUserLoginVm> loginUserOpt = Optional.ofNullable((BaseUserLoginVm)result);
                    loginUserOpt.ifPresent(loginUser -> logRecord.setUserId(loginUser.getData().getId()));
                } else if (parameter instanceof HashMap) {
                    if(((HashMap<?, ?>)parameter).containsKey(DevOpsConstant.API_USER_ID)) {
                        logRecord.setUserId(((HashMap<?, ?>)parameter).get(DevOpsConstant.API_USER_ID).toString());
                    }
                }
            }
            // 异步处理日志记录
            CompletableFuture.runAsync(() -> dosLogRecordService.saveLog(logRecord));
        }
        return result;
    }

    /**
     * 根据方法和传入的参数获取请求参数
     */
    private Object getParameter(Method method, Object[] args) {
        List<Object> argList = new ArrayList<>();
        Parameter[] parameters = method.getParameters();
        Map<String, Object> map = new HashMap<>(16);
        for (int i = 0; i < parameters.length; i++) {
            //将RequestBody注解修饰的参数作为请求参数
            RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
            //将RequestParam注解修饰的参数作为请求参数
            RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);
            String key = parameters[i].getName();
            if (requestBody != null) {
                argList.add(args[i]);
            } else if (requestParam != null) {
                map.put(key, args[i]);
            } else {
                map.put(key, args[i]);
            }
        }
        if (map.size() > 0) {
            argList.add(map);
        }
        if (argList.size() == 0) {
            return null;
        } else if (argList.size() == 1) {
            return argList.get(0);
        } else {
            return argList;
        }
    }

    /**
     * 获取目标主机的ip
     */
    private String getRemoteHost(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        String localhost = "127.0.0.1";
        String oldIp = "0:0:0:0:0:0:0:1";
        String unknown = "unknown";
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }

        return oldIp.equals(ip) ? localhost : ip;
    }
}
